package cn.isjinhao.crws.sql.grammar.retrieve;

import cn.isjinhao.crws.sql.grammar.AbstractParser;
import cn.isjinhao.crws.sql.grammar.BinaryArithmetic;
import cn.isjinhao.crws.sql.grammar.Comparison;
import cn.isjinhao.crws.sql.grammar.CurdStatement;
import cn.isjinhao.crws.sql.grammar.GroupFunction;
import cn.isjinhao.crws.sql.grammar.Grouping;
import cn.isjinhao.crws.sql.grammar.Identifier;
import cn.isjinhao.crws.sql.grammar.Literal;
import cn.isjinhao.crws.sql.grammar.Logic;
import cn.isjinhao.crws.sql.grammar.UnaryArithmetic;
import cn.isjinhao.crws.sql.grammar.ParseException;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.Select;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.SingleSelect;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.group.GroupBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.limit.LimitSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.order.OrderBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.order.OrderBySeg.OrderItem;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.ExistsCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.InCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.RetrieveWhereSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.visitor.SingleSelectMetaInfoDetector;
import cn.isjinhao.crws.sql.lexical.scan.TokenSequence;
import cn.isjinhao.crws.sql.lexical.token.Token;
import cn.isjinhao.crws.sql.lexical.token.TokenType;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author 01395265
 * @date 2021/3/2
 */
public class SelectParser extends AbstractParser<CurdStatement> {

    public SelectParser(TokenSequence tokenSequence) {
        super(tokenSequence);
    }

    /**
     * select               ->  singleSelect (("union" | "minus" | "intersect" | "except") singleSelect)*
     * singleSelect         ->  columnSeg tableSeg (whereSeg)? (groupBySeg)? (orderBySeg)? (limitSeg)?
     *
     * columnSeg            ->  "select" ("distinct" | "first" | "last")? columnRep ("," columnRep)*
     * columnRep            ->  (* | binaryArithmetic) ("as" IDENTIFIER)?
     *
     * tableSeg             ->  "from" tableRep (("left" | "right" | "outer")? ("join" | ",") tableRep ("on" logic)?)*
     * tableRep             ->  ( "(" singleSelect ")" | IDENTIFIER)? IDENTIFIER
     *
     * retrieveWhereSeg     ->  "where" logic
     *
     * groupBySeg           ->  "group" "by" IDENTIFIER ("," IDENTIFIER)* ("having" logic)?
     *
     * orderBySeg           ->  "order" "by" orderItem ("," orderItem)*
     * orderItem            ->  IDENTIFIER ("desc" | "asc")?
     *
     * limitSeg             ->  "limit" number ("," number)?
     *
     * logic                ->  condition (("or" | "and") condition)*
     * condition            ->  inCondition | existsCondition | comparison
     * inCondition          ->  IDENTIFIER ("not")? "in" "(" (single | (primary (, primary)*) ")"
     * existsCondition      ->  ("not")? "exists" "(" singleSelect ")"
     * comparison           ->  binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
     * binaryArithmetic     ->  unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     * unaryArithmetic      ->  ("!"|"-") unaryArithmetic | primary
     * primary              ->  "true" | "false" | "null" | NUMBER | IDENTIFIER | function | STRING | "(" singleSelect ")" | "(" logic ")" | groupFunction
     * function             ->  functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
     * functionName         ->  "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
     * groupFunction        ->  groupFunctionName "(" IDENTIFIER ")"
     * groupFunctionName    ->  "avg" | "max" | "min" | "count" | "sum" | "flat"
     */
    @Override
    public CurdStatement parse() {
        CurdStatement select = select();
        saveSingleSelectContext(select, select, SingleSelectType.TOP);

        tokenSequence.consume(TokenType.EOF, " select 语句未正确结尾！");
        new SingleSelectMetaInfoDetector().accept(select);
        return select;
    }


    /**
     * singleSelect (("union" | "minus" | "intersect" | "except") singleSelect)*
     */
    private CurdStatement select() {
        int flag = 0;
        CurdStatement left = singleSelect();
        while (tokenSequence.curEqual(TokenType.CUNION, TokenType.CMINUS, TokenType.CEXCEPT, TokenType.CINTERSECT)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = singleSelect();
            left = new Select(left, operator, right);
            flag++;
        }
        if(flag == 0) {
            left = new Select(left);
        }
        return left;
    }


    /**
     * columnSeg tableSeg (whereSeg)? (groupBySeg)? (orderBySeg)? (limitSeg)?
     */
    private CurdStatement singleSelect() {
        CurdStatement columnSeg = columnSeg();
        CurdStatement tableSeg = tableSeg();
        CurdStatement whereSeg = null;
        if(tokenSequence.curEqual(TokenType.WHERE)) {
            whereSeg = whereSeg();
        }
        CurdStatement groupBySeg = null;
        if (tokenSequence.curEqual(TokenType.GROUP)) {
            groupBySeg = groupBySeg();
        }
        CurdStatement orderBySeg = null;
        if (tokenSequence.curEqual(TokenType.ORDER)) {
            orderBySeg = orderBySeg();
        }
        CurdStatement limitSeg = null;
        if (tokenSequence.curEqual(TokenType.LIMIT)) {
            limitSeg = limitSeg();
        }
        return new SingleSelect(columnSeg, tableSeg, whereSeg, groupBySeg, orderBySeg, limitSeg);
    }


    /**
     * "select" ("distinct" | "first" | "last")? columnRep ("," columnRep)*
     * (* | binaryArithmetic) ("as" IDENTIFIER)?
     */
    private CurdStatement columnSeg() {
        if(!tokenSequence.curEqual(TokenType.SELECT)) {
            throw new ParseException("需要 select 关键字", tokenSequence);
        }
        tokenSequence.advance();
        Token restriction = null;
        if(tokenSequence.curEqual(TokenType.DISTINCT, TokenType.FIRST, TokenType.LAST)) {
            restriction = tokenSequence.takeCur();
            tokenSequence.advance();
        }
        List<CurdStatement> columnRepList = new ArrayList<>();
        do {
            boolean starFlag = false;
            CurdStatement column;
            if(tokenSequence.curEqual(TokenType.STAR)) {
                starFlag = true;
                column = new Identifier(tokenSequence.takeCur());
                tokenSequence.advance();
            } else {
                column = binaryArithmetic();
            }
            if(tokenSequence.equalThenAdvance(TokenType.AS)) {
                if(starFlag) {
                    throw new ParseException("不能给 '*' 列设置别名", tokenSequence);
                }
                columnRepList.add(new ColumnRep(column, tokenSequence.takeCur()));
                tokenSequence.advance();
            } else {
                columnRepList.add(new ColumnRep(column));
            }
        } while ((tokenSequence.equalThenAdvance(TokenType.COMMA)));
        if (columnRepList.isEmpty()) {
            throw new ParseException("select语句的列至少存在一个", tokenSequence);
        }
        return new ColumnSeg(restriction, columnRepList);
    }


    /**
     * "from" tableRep (("left" | "right" | "outer")? ("join" | ",") tableRep ("on" logic)?)*
     */
    private CurdStatement tableSeg() {
        if(!tokenSequence.curEqual(TokenType.FROM)) {
            throw new ParseException("需要 from 关键字！", tokenSequence);
        }
        tokenSequence.advance();
        CurdStatement left = tableRep();
        while(tokenSequence.curEqual(TokenType.JOIN, TokenType.COMMA, TokenType.LEFT, TokenType.RIGHT, TokenType.OUTER)) {

            Token join = null;
            if(tokenSequence.curEqual(TokenType.LEFT, TokenType.RIGHT, TokenType.OUTER)) {
                join = tokenSequence.takeCur();
                tokenSequence.advance();
            }
            if(join != null) {
                if(tokenSequence.curEqual(TokenType.COMMA)) {
                    throw new ParseException("'outer' 、 'left' 和 'right' 只能修饰 'only' 关键字", tokenSequence);
                } else if(!tokenSequence.curEqual(TokenType.JOIN)) {
                    throw new ParseException("表的连接需要 'join' 或 ',' 关键字", tokenSequence);
                }
                tokenSequence.advance();
            } else {
                if(!tokenSequence.curEqual(TokenType.JOIN, TokenType.COMMA)) {
                    throw new ParseException("表的连接需要 'join' 或 ',' 关键字", tokenSequence);
                } else {
                    join = tokenSequence.takeCur();
                    tokenSequence.advance();
                }
            }
            CurdStatement right = tableRep();
            if(tokenSequence.equalThenAdvance(TokenType.ON)) {
                CurdStatement logic = logic();
                if(!checkLogicOnlyContainsComparison(logic)) {
                    throw new ParseException("表的连接条件不能出现select、in和exists语法", tokenSequence);
                }
                left = new TableSeg(left, join, right, logic);
            } else {
                left = new TableSeg(left, join, right);
            }
        }
        return left;
    }


    /**
     * 检查select语法是不是仅包含 comparison 语法。
     */
    private boolean checkLogicOnlyContainsComparison(CurdStatement curdStatement) {
        if(curdStatement == null) {
            return true;
        }
        if(curdStatement instanceof Grouping) {
            Grouping grouping = (Grouping) curdStatement;
            curdStatement = grouping.getSelectStatement();
        }
        if(curdStatement instanceof Logic) {
            Logic logic = (Logic) curdStatement;
            return checkLogicOnlyContainsComparison(logic.getLeftSelectStatement()) &&
                    checkLogicOnlyContainsComparison(logic.getRightSelectStatement());
        }
        return !(curdStatement instanceof ExistsCondition) && !(curdStatement instanceof InCondition) &&
                !(curdStatement instanceof SingleSelect) && !(curdStatement instanceof Select);
    }


    /**
     * ( "(" singleSelect ")" | IDENTIFIER)? IDENTIFIER
     */
    private CurdStatement tableRep() {
        boolean flag = false;
        CurdStatement curdStatement = null;
        if(checkSingleSelectValue(tokenSequence)) {
            tokenSequence.consume(TokenType.LEFT_PAREN, "(");
            curdStatement = singleSelect();
            tokenSequence.consume(TokenType.RIGHT_PAREN, ")");
            flag = true;
        }
        if(flag && !tokenSequence.curEqual(TokenType.IDENTIFIER)) {
            throw new ParseException("表为select语句构建的临时表时需要别名", tokenSequence);
        }
        Token token = tokenSequence.takeCur();
        tokenSequence.advance();
        if(flag) {
            // 前者是真实表，后者是别名
            TableRep tableRep = new TableRep(curdStatement, token);
            // 存select语句的上下文。
            saveSingleSelectContext(curdStatement, tableRep, SingleSelectType.TABLE);
            return tableRep;
        }
        if(TokenType.IDENTIFIER.equals(tokenSequence.takeCur().getType())) {
            // 在有两个 IDENTIFIER 的时候，前者是真实表，后者是别名
            TableRep tableRep = new TableRep(new Identifier(token), tokenSequence.takeCur());
            tokenSequence.advance();
            return tableRep;
        }
        // 只有一个 IDENTIFIER 的时候，是真实表
        return new TableRep(new Identifier(token));
    }


    /**
     * "where" logic
     */
    private CurdStatement whereSeg() {
        tokenSequence.consume(TokenType.WHERE, "需要 where 关键字！");
        return new RetrieveWhereSeg(logic());
    }


    /**
     * "group" "by" IDENTIFIER ("," IDENTIFIER)* ("having" logic)?
     */
    private CurdStatement groupBySeg() {
        tokenSequence.consume(TokenType.GROUP, "需要 group 关键字");
        tokenSequence.consume(TokenType.BY, "需要 by 关键字");
        List<Token> columnList = new ArrayList<>();
        do {
            columnList.add(tokenSequence.takeCur());
            tokenSequence.advance();
        } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
        if(tokenSequence.equalThenAdvance(TokenType.HAVING)) {
            CurdStatement logic = logic();
            if(!checkLogicOnlyContainsComparison(logic)) {
                throw new ParseException("分组的过滤条件不能出现select、in和exists语法", tokenSequence);
            }
            return new GroupBySeg(columnList, logic);
        }
        return new GroupBySeg(columnList);
    }


    /**
     * "order" "by" orderItem ("," orderItem)*
     * IDENTIFIER ("desc" | "asc")?
     */
    private CurdStatement orderBySeg() {
        tokenSequence.consume(TokenType.ORDER, "需要 order 关键字");
        tokenSequence.consume(TokenType.BY, "需要 by 关键字");
        List<OrderBySeg.OrderItem> columnList = new ArrayList<>();
        do {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            if(tokenSequence.curEqual(TokenType.ASC, TokenType.DESC)) {
                columnList.add(new OrderItem(token, tokenSequence.takeCur()));
                tokenSequence.advance();
            } else {
                columnList.add(new OrderItem(token));
            }
        } while(tokenSequence.equalThenAdvance(TokenType.COMMA));
        return new OrderBySeg(columnList);
    }


    /**
     * "limit" number ("," number)?
     */
    private CurdStatement limitSeg() {
        tokenSequence.consume(TokenType.LIMIT, "需要关键字 limit 。");
        if (!tokenSequence.curEqual(TokenType.NUMBER)){
            throw new ParseException("limit 后面需要 数字！", tokenSequence);
        }
        Token token = tokenSequence.takeCur();
        tokenSequence.advance();
        if(tokenSequence.equalThenAdvance(TokenType.COMMA)) {
            if (!tokenSequence.curEqual(TokenType.NUMBER)){
                throw new ParseException("limit 语法： limit number, number！", tokenSequence);
            }
            LimitSeg limitSeg = new LimitSeg(token, tokenSequence.takeCur());
            tokenSequence.advance();
            return limitSeg;
        }
        return new LimitSeg(token);
    }


    /**
     * inCondition | existsCondition | comparison
     */
    protected CurdStatement condition() {
        if(tokenSequence.curEqual(TokenType.EXISTS, TokenType.NOT)) {
            return existsCondition();
        } else if(tokenSequence.nextEqual(TokenType.NOT, TokenType.IN)) {
            return inCondition();
        }
        return comparison();
    }


    /**
     * IDENTIFIER ("not")? "in" "(" (single | (primary (, primary)*) ")"
     */
    private CurdStatement inCondition() {
        Token identifier = tokenSequence.takeCur();
        tokenSequence.advance();
        Token in = tokenSequence.takeCur();
        if(tokenSequence.curEqual(TokenType.NOT)) {
            tokenSequence.advance();
        }
        tokenSequence.advance();

        if(checkSingleSelectValue(tokenSequence)) {
            tokenSequence.consume(TokenType.LEFT_PAREN, "in作为where条件时，where条件需要由 '()' 括起来");
            CurdStatement curdStatement = select();
            tokenSequence.consume(TokenType.RIGHT_PAREN, "in作为where条件时，where条件需要由 '()' 括起来");
            InCondition inCondition = new InCondition(in, identifier, curdStatement);
            saveSingleSelectContext(curdStatement, inCondition, SingleSelectType.IN);
            return inCondition;
        }
        tokenSequence.consume(TokenType.LEFT_PAREN, "in作为where条件时，where条件需要由 '()' 括起来");
        List<CurdStatement> ranges = new ArrayList<>();
        do {
            CurdStatement primary = primary();
            if(primary instanceof Literal) {
                ranges.add(primary);
            } else {
                throw new ParseException("'in' 后需要接 select语句 或 逗号分割的Literal数组", tokenSequence);
            }
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        tokenSequence.consume(TokenType.RIGHT_PAREN, "in作为where条件时，where条件需要由 '()' 括起来");
        return new InCondition(in, identifier, ranges);
    }


    /**
     * ("not")? "exists" "(" singleSelect ")"
     */
    private CurdStatement existsCondition() {
        Token exists = tokenSequence.takeCur();
        if(tokenSequence.curEqual(TokenType.NOT)) {
            tokenSequence.advance();
        }
        tokenSequence.advance();
        tokenSequence.consume(TokenType.LEFT_PAREN, "(");
        CurdStatement curdStatement = singleSelect();
        tokenSequence.consume(TokenType.RIGHT_PAREN, ")");
        ExistsCondition existsCondition = new ExistsCondition(exists, curdStatement);
        // 存select语句的山下文。
        saveSingleSelectContext(curdStatement, existsCondition, SingleSelectType.EXISTS);
        return existsCondition;
    }


    /**
     * binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
     */
    @Override
    protected CurdStatement comparison() {
        CurdStatement curdStatement = super.comparison();
        if(curdStatement instanceof Comparison) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     */
    @Override
    protected CurdStatement binaryArithmetic() {
        CurdStatement curdStatement = super.binaryArithmetic();
        if(curdStatement instanceof BinaryArithmetic) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * ("!"|"-") unaryArithmetic | primary
     */
    @Override
    protected CurdStatement unaryArithmetic() {
        CurdStatement curdStatement = super.unaryArithmetic();
        if(curdStatement instanceof UnaryArithmetic) {
            saveSingleSelectContext(curdStatement, curdStatement, SingleSelectType.PRIMARY);
        }
        return curdStatement;
    }


    /**
     * "true" | "false" | "null" | NUMBER | STRING | IDENTIFIER | function | groupFunction | "(" singleSelect ")" | "(" logic ")"
     */
    @Override
    protected CurdStatement primary() {
        CurdStatement primary = super.primary();
        if(primary != null) {
            return primary;
        }

        // IDENTIFIER
        if (tokenSequence.equalThenAdvance(TokenType.IDENTIFIER)) {
            return new Identifier(tokenSequence.takePre());
        }

        // groupFunction | function
        Token token = tokenSequence.takeCur();
        if(checkGroupFunctionName(token)) {
            return groupFunction();
        } else if (checkFunctionName(token)) {
            return function();
        }

        // singleSelect
        if(checkSingleSelectValue(tokenSequence)) {
            tokenSequence.consume(TokenType.LEFT_PAREN, "(");
            SingleSelect singleSelect = (SingleSelect)singleSelect();
            tokenSequence.consume(TokenType.RIGHT_PAREN, ")");
            return singleSelect;
        }

        // "(" logic ")"
        if (tokenSequence.equalThenAdvance(TokenType.LEFT_PAREN)) {
            CurdStatement curdStatement = logic();
            tokenSequence.consume(TokenType.RIGHT_PAREN, "Expect ')'");
            return new Grouping(curdStatement);
        }

        throw new ParseException("无法解析当前token: " + token, tokenSequence);
    }


    private void saveSingleSelectContext(CurdStatement curdStatement, CurdStatement parent, SingleSelectType type) {
        if(curdStatement instanceof SingleSelect) {
            SingleSelect singleSelect = (SingleSelect) curdStatement;
            if(singleSelect.getSingleSelectType() == null) {
                singleSelect.setSingleSelectType(type);
                singleSelect.setParentSelectStatement(parent);
            }
        } else if(curdStatement instanceof Select) {
            Select select = (Select) curdStatement;
            saveSingleSelectContext(select.getLeftSelectStatement(), select, type);
            saveSingleSelectContext(select.getRightSelectStatement(), select, type);
        } else if(curdStatement instanceof BinaryArithmetic) {
            BinaryArithmetic binaryArithmetic = (BinaryArithmetic) binaryArithmetic();
            saveSingleSelectContext(binaryArithmetic.getLeftSelectStatement(), binaryArithmetic, type);
            saveSingleSelectContext(binaryArithmetic.getRightSelectStatement(), binaryArithmetic, type);
        } else if(curdStatement instanceof UnaryArithmetic) {
            UnaryArithmetic unaryArithmetic = (UnaryArithmetic) curdStatement;
            saveSingleSelectContext(unaryArithmetic.getSelectStatement(), unaryArithmetic, type);
        } else if(curdStatement instanceof Comparison) {
            Comparison comparison = (Comparison) curdStatement;
            saveSingleSelectContext(comparison.getLeftSelectStatement(), comparison, type);
            saveSingleSelectContext(comparison.getRightSelectStatement(), comparison, type);
        }
    }


    /**
     * ("avg" | "max" | "min" | "count" | "sum", "flat") "(" IDENTIFIER ")"
     */
    protected CurdStatement groupFunction() {
        Token method = tokenSequence.takeCur();
        tokenSequence.advance();
        tokenSequence.consume(TokenType.LEFT_PAREN, "Expect '('");
        Token identifier = tokenSequence.takeCur();
        tokenSequence.advance();
        tokenSequence.consume(TokenType.RIGHT_PAREN, "Expect ')'");
        return new GroupFunction(method, identifier);
    }

    protected static Set<TokenType> groupFunctionNameSet = new HashSet<>();
    static {
        groupFunctionNameSet.add(TokenType.MAX);
        groupFunctionNameSet.add(TokenType.MIN);
        groupFunctionNameSet.add(TokenType.FLAT);
        groupFunctionNameSet.add(TokenType.COUNT);
        groupFunctionNameSet.add(TokenType.AVG);
        groupFunctionNameSet.add(TokenType.SUM);
    }

    protected boolean checkGroupFunctionName(Token token) {
        return groupFunctionNameSet.contains(token.getType());
    }

    private boolean checkSingleSelectValue(TokenSequence tokenSequence) {
        Token cur = tokenSequence.takeCur();
        Token next = tokenSequence.takeNext();
        return TokenType.LEFT_PAREN.equals(cur.getType()) && TokenType.SELECT.equals(next.getType());
    }

}
